package com.m3958.logcollector.config;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.acls.AclPermissionEvaluator;
import org.springframework.security.acls.domain.AclAuthorizationStrategy;
import org.springframework.security.acls.domain.AclAuthorizationStrategyImpl;
import org.springframework.security.acls.domain.AuditLogger;
import org.springframework.security.acls.domain.ConsoleAuditLogger;
import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy;
import org.springframework.security.acls.domain.SpringCacheBasedAclCache;
import org.springframework.security.acls.jdbc.BasicLookupStrategy;
import org.springframework.security.acls.jdbc.JdbcAclService;
import org.springframework.security.acls.jdbc.JdbcMutableAclService;
import org.springframework.security.acls.jdbc.LookupStrategy;
import org.springframework.security.acls.model.AclCache;
import org.springframework.security.acls.model.AclService;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.PermissionGrantingStrategy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import com.google.common.collect.Sets;
import com.m3958.logcollector.passwordvalidator.RepeatValidator;
import com.m3958.logcollector.passwordvalidator.PasswordValidateMethod;
import com.m3958.logcollector.passwordvalidator.PasswordValidator;
import com.m3958.logcollector.passwordvalidator.PasswordValidatorConfig;
import com.m3958.logcollector.passwordvalidator.SequenceValidator;
import com.m3958.logcollector.userdetail.PersonManager;
import com.m3958.logcollector.userdetail.PersonManagerConfigurer;
import com.m3958.logcollector.vo.RoleNames;
import com.m3958.logcollector.vo.RoleVo;

/**
 * 1、如果标注了@Configuration和@EnableWebSecurity，那么就关闭了spring-boot的web安全的设置
 * 2、可以提供自己的AuthenticationManager，那么默认的就不会创建，同时你有了完全的设置控制
 * 3、在WebSecurityConfigurerAdapter的继承类中，标注@Configuration，同时@Autowired.
 * 4、继承GlobalAuthenticationConfigurerAdapter，同时override init方法。（If you experience instantiation issues）
 * 。
 * 
 * @author jianglibo
 *
 *         2015年4月22日-上午10:44:07
 */
@Configuration
@EnableWebSecurity
//@Order(1)
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecConfig extends WebSecurityConfigurerAdapter {

    private static Logger logger = LoggerFactory.getLogger(WebSecConfig.class);

    @Autowired
    private AppConfig appConfig;

    @Autowired
    private PasswordValidatorConfig passwordConfig;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private PersonManager personManager;

    @Value("${spring.data.rest.base-path}")
    private String apiPrefix;

    /**
     * disable default. then read father class's gethttp method. write all config your self.
     */

    public WebSecConfig() {
        super(true);
    }

    @Bean
    public PasswordValidator passwordValidator() {
        List<PasswordValidateMethod> pvms = new ArrayList<>();
        
        if (passwordConfig.getMaxRepeat() > 0) {
            pvms.add(new RepeatValidator(passwordConfig.getMaxRepeat()));
        }
        
        if (passwordConfig.getMaxSequence() > 0) {
            pvms.add(new SequenceValidator(passwordConfig.getMaxSequence()));
        }
        
        return new PasswordValidator(passwordConfig.getMinLength(), //
                passwordConfig.getMaxLength(), pvms.toArray(new PasswordValidateMethod[]{}));
    }

    @Bean
    public LogCollectorExceptionTranslationFilter logCollectorExceptionTranslationFilter() {
        String loginEntry = appConfig.getLoginEntryPoint();
        if (loginEntry == null) {
            loginEntry = "/login";
        }
        AuthenticationEntryPoint authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(loginEntry);
        return new LogCollectorExceptionTranslationFilter(authenticationEntryPoint);
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        PersonManagerConfigurer<AuthenticationManagerBuilder> pc = auth.apply(new PersonManagerConfigurer<AuthenticationManagerBuilder>(personManager))
                .passwordEncoder(passwordEncoder);
    };

    /**
     * 如果用户请求protected resource，产生accessdenyexception，此时，exceptionhandler拦截这个exception，找到默认的entryPoint，根据类型采取相应的措施，常见的是重定向到formlogin页面。
     * 表单里面输入username，password，然后由对应的authenticationfilter处理。我们可以查看一下UsernamePasswordAuthenticationFilter做了什么，然后模仿一个即可。
     * 
     * 是formLogin()产生了这个配置，那么除去formLogin，然后按照formLogin编写自己的配置就可以了。
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        String taskListUri = apiPrefix; // + ResourceNameConstants.TASK_LIST_PATH;

        // copy from father class's getHttp method.
        http.csrf().requireCsrfProtectionMatcher(new M3958RequiresCsrfMatcher(taskListUri, "/v1/taskitems")).and()
                .addFilter(new WebAsyncManagerIntegrationFilter())
                // .exceptionHandling().and()
                .headers().and().addFilter(logCollectorExceptionTranslationFilter()).sessionManagement()
                .sessionAuthenticationStrategy(sessionAuthenticationStrategy()).and().securityContext().and()
                // .addFilterAfter(shBasicFilter(), SecurityContextPersistenceFilter.class) //莫名其妙，必须同时在这里配置，还有oauthresource配置。
                .httpBasic().and().requestCache().and().anonymous().and().servletApi().and()
                // .apply(new DefaultLoginPageConfigurer<HttpSecurity>()).and()
                .logout().logoutSuccessUrl(appConfig.getLogoutSuccessUrl());

        http.authorizeRequests()
                .antMatchers("/", //
                        "/llapp/**", //
                        "/notapp/**", //
                        "/login/**", //
                        "/favicon.ico", //
                        "/tkconsumer", //
                        "/keypairs", //
                        "/demo", //
                        "/shusers", //
                        "/resettoqueueing", //
                        "/upload", //
                        "/runtime/*", //
                        "/very-long-cache/**")//
                .permitAll().anyRequest().fullyAuthenticated().and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(11);
    }

    @Bean
    public ChangeSessionIdAuthenticationStrategy sessionAuthenticationStrategy() {
        return new ChangeSessionIdAuthenticationStrategy();
    }

    private static final class M3958RequiresCsrfMatcher implements RequestMatcher {
        private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");

        private Set<String> uris;

        public M3958RequiresCsrfMatcher(String... uri) {
            this.uris = Sets.newHashSet(uri);
        }

        public boolean matches(HttpServletRequest request) {
            boolean needCsrf = !allowedMethods.matcher(request.getMethod()).matches();

            if (needCsrf) {
                if (uris.contains((request.getRequestURI()))) { // 仅仅对此目录，不需要csrf
                    needCsrf = false;
                }
            }
            return needCsrf;
        }
    }
}
